package mlog

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"go.uber.org/multierr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

// 基于zap做的日志记录

type Logger struct {
	zl *zap.Logger

	Config   LogConfig
	Skip     int //堆栈深度，默认最小值2
	LogLevel Level
}

// NewLog 根据配置，创建Logger
func NewLog(config LogConfig, skip ...int) *Logger {
	logMode := zapcore.InfoLevel
	logMode.Set(config.Level)

	s := 2
	if len(skip) > 0 {
		s = skip[0]
	}

	strCheck := make(map[string]bool)
	var tmpOutputs []string
	var stdWriteList, fileWriteList []zapcore.WriteSyncer
	for _, str := range config.Outputs {
		if str == "" {
			continue
		}
		if _, ok := strCheck[strings.ToLower(str)]; ok {
			continue
		}
		strCheck[strings.ToLower(str)] = true
		tmpOutputs = append(tmpOutputs, str)
		switch strings.ToLower(str) {
		case "stdout":
			stdWriteList = append(stdWriteList, zapcore.AddSync(os.Stdout))
		case "stderr":
			stdWriteList = append(stdWriteList, zapcore.AddSync(os.Stderr))
		default:
			fileWriteList = append(fileWriteList, getFileWriterSyncer(str, config))
		}
	}
	config.Outputs = tmpOutputs
	if len(stdWriteList) == 0 && len(fileWriteList) == 0 { //默认输出
		stdWriteList = append(stdWriteList, zapcore.AddSync(os.Stdout))
	}
	//区分打印输出和文件输出。文件输出不使用颜色
	var coreList []zapcore.Core
	if len(stdWriteList) > 0 {
		core := zapcore.NewCore(getEncoder(config), zapcore.NewMultiWriteSyncer(stdWriteList...), logMode)
		coreList = append(coreList, core)
	}
	if len(fileWriteList) > 0 {
		fileConfig := config
		fileConfig.LevelColor = false
		core := zapcore.NewCore(getEncoder(fileConfig), zapcore.NewMultiWriteSyncer(fileWriteList...), logMode)
		coreList = append(coreList, core)
	}

	core := zapcore.NewTee(coreList...)
	return &Logger{
		zl:       zap.New(core, zap.AddCaller(), zap.AddCallerSkip(s)),
		Config:   config,
		Skip:     s,
		LogLevel: Level(logMode),
	}
}

// 定义的日志输出格式
func getEncoder(config LogConfig) zapcore.Encoder {
	timeFormat := config.TimeFormat
	if timeFormat == "" {
		timeFormat = "2006/01/02 15:04:05"
	}
	// 默认部分输出格式
	encoderConfig := zap.NewDevelopmentEncoderConfig()
	{
		// LevelKey值变为 level
		encoderConfig.LevelKey = "level"
		// MessageKey值变为 msg
		encoderConfig.MessageKey = "msg"
		// TimeKey值 变成time
		encoderConfig.TimeKey = "time"
		encoderConfig.CallerKey = "file"
		encoderConfig.StacktraceKey = "stack"
		// 把输出的info 变成INFO 只需要丢对象 不许执行
		if config.LevelColor {
			encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
		} else {
			encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
		}

		// 对时间进行格式化处理
		encoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Local().Format(timeFormat))
		}
		//日志记录位置信息
		switch strings.ToLower(config.CallerType) {
		case "full": //完整文件路径
			encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
		case "short": //包/文件名
			encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
		default: //one 文件名
			encoderConfig.EncodeCaller = oneCallerEncoder
		}
	}
	if config.Encoder != nil {
		if config.Encoder.LevelKey != "" {
			encoderConfig.LevelKey = config.Encoder.LevelKey
		}
		if config.Encoder.MessageKey != "" {
			encoderConfig.MessageKey = config.Encoder.MessageKey
		}
		if config.Encoder.TimeKey != "" {
			encoderConfig.TimeKey = config.Encoder.TimeKey
		}
		if config.Encoder.CallerKey != "" {
			encoderConfig.CallerKey = config.Encoder.CallerKey
		}
		//if config.Encoder.NameKey != "" {
		//	encoderConfig.LevelKey = config.Encoder.NameKey
		//}
		if config.Encoder.FunctionKey != "" {
			encoderConfig.FunctionKey = config.Encoder.FunctionKey
		}
		if config.Encoder.StacktraceKey != "" {
			encoderConfig.StacktraceKey = config.Encoder.StacktraceKey
		}
		if config.Encoder.LineEnding != "" {
			encoderConfig.LineEnding = config.Encoder.LineEnding
		}

		if config.Encoder.ConsoleSeparator != "" {
			encoderConfig.ConsoleSeparator = config.Encoder.ConsoleSeparator
		}

	}

	if config.Format == "json" {
		return zapcore.NewJSONEncoder(encoderConfig)
	} else {
		return zapcore.NewConsoleEncoder(encoderConfig)
	}
}

// 日志输出到文件
func getFileWriterSyncer(fileName string, config LogConfig) zapcore.WriteSyncer {
	if fileName == "" {
		stSeparator := string(filepath.Separator)
		stRootDir, _ := os.Getwd()
		fileName = stRootDir + stSeparator + "log" + stSeparator + time.Now().Format("2006-01-02") + ".log"
	}

	// 日志分割保存
	hook := lumberjack.Logger{
		Filename:   fileName,          // 日志文件路径，默认 os.TempDir()
		MaxSize:    config.MaxSize,    // 每个日志文件保存500M，默认 100M
		MaxBackups: config.MaxBackups, // 保留3个备份，默认不限
		MaxAge:     config.MaxAge,     // 保留28天，默认不限
		Compress:   config.Compress,   // 是否压缩，默认不压缩
	}

	return zapcore.AddSync(&hook)
}

// 单个文件记录
func oneCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
	if !caller.Defined {
		enc.AppendString("undefined")
		return
	}

	idx := strings.LastIndexByte(caller.File, '/')
	if idx == -1 {
		enc.AppendString(caller.FullPath())
		return
	}

	enc.AppendString(fmt.Sprintf("%s:%d", caller.File[idx+1:], int64(caller.Line)))
}

func (m *Logger) GetConfig() LogConfig {
	return m.Config
}

func getMessage(template string, fmtArgs []interface{}) string {
	if len(fmtArgs) == 0 {
		return template
	}

	if template != "" {
		return fmt.Sprintf(template, fmtArgs...)
	}

	if len(fmtArgs) == 1 {
		if str, ok := fmtArgs[0].(string); ok {
			return str
		}
	}
	return fmt.Sprint(fmtArgs...)
}

func (m *Logger) log(lv Level, format string, fmtArgs []interface{}, context []interface{}) {
	if !m.LevelEnabled(lv) {
		return
	}
	msg := getMessage(format, fmtArgs)
	fields := m.sweetenFields(context)
	switch lv {
	case DebugLevel:
		m.zl.Debug(msg, fields...)
	case InfoLevel:
		m.zl.Info(msg, fields...)
	case WarnLevel:
		m.zl.Warn(msg, fields...)
	case ErrorLevel:
		m.zl.Error(msg, fields...)
	case DPanicLevel:
		m.zl.DPanic(msg, fields...)
	case PanicLevel:
		m.zl.Panic(msg, fields...)
	case FatalLevel:
		m.zl.Fatal(msg, fields...)
	}
}

func (m *Logger) sweetenFields(args []interface{}) []zap.Field {
	if len(args) == 0 {
		return nil
	}

	// Allocate enough space for the worst case; if users pass only structured
	// fields, we shouldn't penalize them with extra allocations.
	fields := make([]zap.Field, 0, len(args))
	var invalid invalidPairs

	for i := 0; i < len(args); {
		// This is a strongly-typed field. Consume it and move on.
		if f, ok := args[i].(zap.Field); ok {
			fields = append(fields, f)
			i++
			continue
		} else if f, ok := args[i].([]zap.Field); ok {
			fields = append(fields, f...)
			i++
			continue
		}

		// Make sure this element isn't a dangling key.
		if i == len(args)-1 {
			m.zl.Error("Ignored key without a value.", zap.Any("ignored", args[i]))

			break
		}

		// Consume this value and the next, treating them as a key-value pair. If the
		// key isn't a string, add this pair to the slice of invalid pairs.
		key, val := args[i], args[i+1]
		if keyStr, ok := key.(string); !ok {
			// Subsequent errors are likely, so allocate once up front.
			if cap(invalid) == 0 {
				invalid = make(invalidPairs, 0, len(args)/2)
			}
			invalid = append(invalid, invalidPair{i, key, val})
		} else {
			fields = append(fields, zap.Any(keyStr, val))
		}
		i += 2
	}

	// If we encountered any invalid key-value pairs, log an error.
	if len(invalid) > 0 {
		m.zl.Error("Ignored key-value pairs with non-string keys.", zap.Array("invalid", invalid))
	}
	return fields
}

func (m *Logger) GetLogLevel() Level {
	return m.LogLevel
}

func (m *Logger) LevelEnabled(lv Level) bool {
	return m.LogLevel <= lv
}

func (m *Logger) DebugEnabled() bool {
	return m.LevelEnabled(DebugLevel)
}

func (m *Logger) InfoEnabled() bool {
	return m.LevelEnabled(InfoLevel)
}

func (m *Logger) WarnEnabled() bool {
	return m.LevelEnabled(WarnLevel)
}

func (m *Logger) ErrorEnabled() bool {
	return m.LevelEnabled(ErrorLevel)
}

func (m *Logger) PanicEnabled() bool {
	return m.LevelEnabled(PanicLevel)
}

func (m *Logger) FatalEnabled() bool {
	return m.LevelEnabled(FatalLevel)
}

// 日志记录

// Output 自定义增加深度 addCallerSkip：在原来基础上增加的堆栈深度
// WithOptions是克隆生成新对象，不影响原对象，也不会保存新对象
func (m *Logger) Output(addCallerSkip int, lv Level, msg string, fields ...zap.Field) {
	if !m.LevelEnabled(lv) {
		return
	}
	switch lv {
	case DebugLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Debug(msg)
	case InfoLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Info(msg)
	case WarnLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Warn(msg)
	case ErrorLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Error(msg)
	case DPanicLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).DPanic(msg)
	case PanicLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Panic(msg)
	case FatalLevel:
		m.zl.WithOptions(zap.AddCallerSkip(addCallerSkip)).Fatal(msg)
	}
}

func (m *Logger) Debug(args ...interface{}) {
	m.log(DebugLevel, "", args, nil)
}

func (m *Logger) Info(args ...interface{}) {
	m.log(InfoLevel, "", args, nil)
}

func (m *Logger) Warn(args ...interface{}) {
	m.log(WarnLevel, "", args, nil)
}

func (m *Logger) Error(args ...interface{}) {
	m.log(ErrorLevel, "", args, nil)
}

func (m *Logger) DPanic(args ...interface{}) {
	m.log(DPanicLevel, "", args, nil)
}

func (m *Logger) Panic(args ...interface{}) {
	m.log(PanicLevel, "", args, nil)
}

func (m *Logger) Fatal(args ...interface{}) {
	m.log(FatalLevel, "", args, nil)
}

func (m *Logger) Debugf(format string, args ...interface{}) {
	m.log(DebugLevel, format, args, nil)
}

func (m *Logger) Infof(format string, args ...interface{}) {
	m.log(InfoLevel, format, args, nil)
}

func (m *Logger) Warnf(format string, args ...interface{}) {
	m.log(WarnLevel, format, args, nil)
}

func (m *Logger) Errorf(format string, args ...interface{}) {
	m.log(ErrorLevel, format, args, nil)
}

func (m *Logger) DPanicf(format string, args ...interface{}) {
	m.log(DPanicLevel, format, args, nil)
}

func (m *Logger) Panicf(format string, args ...interface{}) {
	m.log(PanicLevel, format, args, nil)
}

func (m *Logger) Fatalf(format string, args ...interface{}) {
	m.log(FatalLevel, format, args, nil)
}

func (m *Logger) Debugw(msg string, keyValues ...interface{}) {
	m.log(DebugLevel, msg, nil, keyValues)
}

func (m *Logger) Infow(msg string, keyValues ...interface{}) {
	m.log(InfoLevel, msg, nil, keyValues)
}

func (m *Logger) Warnw(msg string, keyValues ...interface{}) {
	m.log(WarnLevel, msg, nil, keyValues)
}

func (m *Logger) Errorw(msg string, keyValues ...interface{}) {
	m.log(ErrorLevel, msg, nil, keyValues)
}

func (m *Logger) DPanicw(msg string, keyValues ...interface{}) {
	m.log(DPanicLevel, msg, nil, keyValues)
}

func (m *Logger) Panicw(msg string, keyValues ...interface{}) {
	m.log(PanicLevel, msg, nil, keyValues)
}

func (m *Logger) Fatalw(msg string, keyValues ...interface{}) {
	m.log(FatalLevel, msg, nil, keyValues)
}

type invalidPair struct {
	position   int
	key, value interface{}
}

func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddInt64("position", int64(p.position))
	zap.Any("key", p.key).AddTo(enc)
	zap.Any("value", p.value).AddTo(enc)
	return nil
}

type invalidPairs []invalidPair

func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error {
	var err error
	for i := range ps {
		err = multierr.Append(err, enc.AppendObject(ps[i]))
	}
	return err
}
